package com.starview.star.compiler.generate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.Stack;

import com.starview.platform.data.IArray;
import com.starview.platform.data.IValue;
import com.starview.platform.data.type.ConstructorSpecifier;
import com.starview.platform.data.type.IAlgebraicType;
import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.IValueSpecifier;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.RecordSpecifier;
import com.starview.platform.data.type.Refresher;
import com.starview.platform.data.type.TypeDescription;
import com.starview.platform.data.type.TypeInterface;
import com.starview.platform.data.type.TypeInterfaceType;
import com.starview.platform.data.type.TypeUtils;
import com.starview.platform.data.type.TypeVar;
import com.starview.platform.data.value.Array;
import com.starview.platform.data.value.NTuple;
import com.starview.platform.data.value.ResourceURI;
import com.starview.star.compiler.CompilerUtils;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.ast.Abstract;
import com.starview.star.compiler.ast.Apply;
import com.starview.star.compiler.ast.IAbstract;
import com.starview.star.compiler.ast.Name;
import com.starview.star.compiler.cafe.CafeSyntax;
import com.starview.star.compiler.canonical.Application;
import com.starview.star.compiler.canonical.AssertAction;
import com.starview.star.compiler.canonical.Assignment;
import com.starview.star.compiler.canonical.CaseAction;
import com.starview.star.compiler.canonical.CaseExpression;
import com.starview.star.compiler.canonical.CastExpression;
import com.starview.star.compiler.canonical.CastPtn;
import com.starview.star.compiler.canonical.ConditionCondition;
import com.starview.star.compiler.canonical.ConditionalAction;
import com.starview.star.compiler.canonical.ConditionalExp;
import com.starview.star.compiler.canonical.Conjunction;
import com.starview.star.compiler.canonical.ConstructorPtn;
import com.starview.star.compiler.canonical.ConstructorTerm;
import com.starview.star.compiler.canonical.ContentCondition;
import com.starview.star.compiler.canonical.Disjunction;
import com.starview.star.compiler.canonical.EnvironmentEntry.ImportEntry;
import com.starview.star.compiler.canonical.ExceptionHandler;
import com.starview.star.compiler.canonical.FalseCondition;
import com.starview.star.compiler.canonical.FieldAccess;
import com.starview.star.compiler.canonical.FunctionLiteral;
import com.starview.star.compiler.canonical.ICondition;
import com.starview.star.compiler.canonical.IContentAction;
import com.starview.star.compiler.canonical.IContentExpression;
import com.starview.star.compiler.canonical.IContentPattern;
import com.starview.star.compiler.canonical.IStatement;
import com.starview.star.compiler.canonical.Ignore;
import com.starview.star.compiler.canonical.IsTrue;
import com.starview.star.compiler.canonical.JavaEntry;
import com.starview.star.compiler.canonical.LetAction;
import com.starview.star.compiler.canonical.LetTerm;
import com.starview.star.compiler.canonical.Matches;
import com.starview.star.compiler.canonical.MatchingPattern;
import com.starview.star.compiler.canonical.MemoExp;
import com.starview.star.compiler.canonical.MethodVariable;
import com.starview.star.compiler.canonical.Negation;
import com.starview.star.compiler.canonical.NullAction;
import com.starview.star.compiler.canonical.NullExp;
import com.starview.star.compiler.canonical.Otherwise;
import com.starview.star.compiler.canonical.OverloadedFieldAccess;
import com.starview.star.compiler.canonical.OverloadedVariable;
import com.starview.star.compiler.canonical.PackageTerm;
import com.starview.star.compiler.canonical.PatternAbstraction;
import com.starview.star.compiler.canonical.PatternApplication;
import com.starview.star.compiler.canonical.ProcedureCallAction;
import com.starview.star.compiler.canonical.RaiseAction;
import com.starview.star.compiler.canonical.RaiseExpression;
import com.starview.star.compiler.canonical.RecordPtn;
import com.starview.star.compiler.canonical.RecordSubstitute;
import com.starview.star.compiler.canonical.RecordTerm;
import com.starview.star.compiler.canonical.RegExpPattern;
import com.starview.star.compiler.canonical.Resolved;
import com.starview.star.compiler.canonical.Scalar;
import com.starview.star.compiler.canonical.ScalarPtn;
import com.starview.star.compiler.canonical.Search;
import com.starview.star.compiler.canonical.Sequence;
import com.starview.star.compiler.canonical.Shriek;
import com.starview.star.compiler.canonical.SyncAction;
import com.starview.star.compiler.canonical.TrueCondition;
import com.starview.star.compiler.canonical.TypeDefinition;
import com.starview.star.compiler.canonical.ValisAction;
import com.starview.star.compiler.canonical.ValofExp;
import com.starview.star.compiler.canonical.VarDeclaration;
import com.starview.star.compiler.canonical.VarEntry;
import com.starview.star.compiler.canonical.Variable;
import com.starview.star.compiler.canonical.VoidExp;
import com.starview.star.compiler.canonical.WherePattern;
import com.starview.star.compiler.canonical.WhileAction;
import com.starview.star.compiler.canonical.Yield;
import com.starview.star.compiler.sources.JavaImport;
import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.transform.FlowAnalysis;
import com.starview.star.compiler.transform.VarAnalysis.VarChecker;
import com.starview.star.compiler.type.BindingKind;
import com.starview.star.compiler.util.AccessMode;
import com.starview.star.compiler.util.ContinueFlag;
import com.starview.star.compiler.util.EntryVisitor;
import com.starview.star.compiler.util.FixedList;
import com.starview.star.compiler.util.GenSym;
import com.starview.star.compiler.util.Pair;
import com.starview.star.compiler.util.Wrapper;
import com.starview.star.operators.Intrinsics;
import com.starview.star.operators.assignment.runtime.Assignments.Assign;
import com.starview.star.operators.assignment.runtime.Assignments.AssignRawBool;
import com.starview.star.operators.assignment.runtime.Assignments.AssignRawChar;
import com.starview.star.operators.assignment.runtime.Assignments.AssignRawFloat;
import com.starview.star.operators.assignment.runtime.Assignments.AssignRawInteger;
import com.starview.star.operators.assignment.runtime.Assignments.AssignRawLong;
import com.starview.star.operators.assignment.runtime.GetRefValue.GetRawBoolRef;
import com.starview.star.operators.assignment.runtime.GetRefValue.GetRawCharRef;
import com.starview.star.operators.assignment.runtime.GetRefValue.GetRawFloatRef;
import com.starview.star.operators.assignment.runtime.GetRefValue.GetRawIntegerRef;
import com.starview.star.operators.assignment.runtime.GetRefValue.GetRawLongRef;
import com.starview.star.operators.assignment.runtime.GetRefValue.GetRef;
import com.starview.star.operators.spawn.runtime.NotifyWait.Notify;
import com.starview.star.operators.spawn.runtime.NotifyWait.Wait;

/**
 * This implements the Cafe generation phase of the compiler.
 * 
 * Handles the assignment of variables to offsets etc.
 * 
 * We use an internal hash map to make it easier to keep track of what functions compile what
 * elements.
 * 
 * @author fgm
 * 
 */
public class CafeGenerator
{
  private final ErrorReport errors;

  private Map<Class<? extends IContentExpression>, ExpressionGenerator> expressionGenerators = new HashMap<Class<? extends IContentExpression>, ExpressionGenerator>();
  private Map<Class<? extends IContentAction>, IActionCompiler> actionGenerators = new HashMap<Class<? extends IContentAction>, IActionCompiler>();
  private Map<Class<? extends IContentPattern>, PatternGenerator> patternGenerators = new HashMap<Class<? extends IContentPattern>, PatternGenerator>();

  private Stack<List<IAbstract>> scopeStack = new Stack<List<IAbstract>>();

  private List<IAbstract> pkgDefs = new ArrayList<IAbstract>();
  {
    // Expressions
    install(new GenerateRecord());
    install(new GenerateSubstitute());
    install(new GenerateCaseExp());
    install(new CompileCast());
    install(new CompileConditionalExp());
    install(new CompileContentCondition());
    install(new GenerateApplication());
    install(new CompileFieldAccess());
    install(new CompileFunctionLiteral());
    install(new CompileLetTerm());
    install(new CompileMemo());
    install(new CompileMethodVariable());
    install(new CompileNullExp());
    install(new CompileOverloadedVariable());
    install(new CompileOverloadedFieldAccess());
    install(new CompilePatternAbstraction());
    install(new CompileRaise());
    install(new CompilerResolved());
    install(new CompileScalar());
    install(new CompileShriek());
    install(new CompileTuple());
    install(new CompileValof());
    install(new CompileVariable());
    install(new CompileVoid());

    // Patterns
    install(new GenerateAbstractionCall());
    install(new PtnAggregate());
    install(new PtnCast());
    install(new PtnMatching());
    install(new PtnOverloadedVariable());
    install(new ScalarPtnGenerator());
    install(new PtnRegExp());
    install(new PtnTuple());
    install(new PtnWhere());
    install(new PtnVariable());

    // Actions
    install(new CompileAssert());
    install(new CompileAssignment());

    install(new CompileCaseAction());
    install(new CompileConditionalAction());
    install(new CompileExceptionHandler());
    install(new CompileIgnored());
    install(new CompileLetAction());
    install(new CompileLoopAction());
    install(new CompileNullAction());
    install(new CompileProcedureCallAction());
    install(new CompileRaiseAction());
    install(new CompileSequence());
    install(new CompileSyncAction());
    install(new CompileValisAction());
    install(new CompileVarDeclaration());
    install(new CompileYield());
  }

  public CafeGenerator(ErrorReport errors)
  {
    this.errors = errors;
  }

  public static IArray generatePackage(PackageTerm pkg, ErrorReport errors)
  {
    CafeGenerator comp = new CafeGenerator(errors);

    List<IAbstract> pkgDefs = comp.pkgDefs;
    Location pkgLoc = pkg.getLoc();
    CContext pkgCxt = new CContext(pkgLoc);

    for (Pair<Location, ResourceURI> im : pkg.getImports())
      pkgDefs.add(CafeSyntax.importSpec(im.left(), im.right()));

    Set<String> types = new HashSet<String>();

    Variable pkgVar = new Variable(pkgLoc, pkg.getPkgType(), pkg.getPkgName());
    pkgDefs.addAll(comp.compileVarDeclaration(pkgLoc, pkgVar, AccessMode.readOnly, pkg.getPkgValue(), pkgCxt));

    for (TypeDefinition type : pkg.getTypes())
      if (!types.contains(type.getName()) && !type.isImported()) {
        comp.generateType(type.getTypeDescription(), pkgDefs, pkgCxt);
        types.add(type.getName());
      }
    for (IAlgebraicType extra : pkgCxt.getIntroducedTypes().values())
      if (!types.contains(extra.getName())) {
        comp.generateType(extra, pkgDefs, pkgCxt);
        types.add(extra.getName());
      }

    return new Array(pkgDefs);
  }

  // cafe interface type are v. similar to normal interfaces; except for the handling of overloaded
  // names.
  private static void introduceAnonRecordType(CContext cxt, TypeInterfaceType face)
  {
    String label = TypeUtils.anonRecordLabel(face);

    if (!Intrinsics.isIntrinsicType(label)) {
      IAlgebraicType desc = cxt.introducedType(label);

      if (desc == null) {
        List<IType> argTypes = new ArrayList<IType>();
        Map<String, IType> members = face.getAllFields();
        Map<String, IType> vMembers = new HashMap<String, IType>();

        for (Entry<String, IType> entry : members.entrySet()) {
          IType v = new TypeVar();
          String fieldName = entry.getKey();
          vMembers.put(fieldName, v);
          argTypes.add(v);
        }

        IType faceType = TypeUtils.typeExp(label, argTypes);
        IType conType = Refresher.generalize(TypeUtils.constructorType(new TypeInterfaceType(vMembers), faceType));
        RecordSpecifier record = new RecordSpecifier(cxt.getLoc(), label, null, 0, conType);
        desc = new TypeDescription(cxt.getLoc(), faceType, record);
        cxt.introduceType(desc);
      }
    }
  }

  private static IAbstract typeToAbstract(Location loc, final CContext cxt, IType type)
  {
    TypeAbstract converter = new TypeAbstract(loc) {

      @Override
      public void visitTypeInterface(TypeInterfaceType t)
      {
        super.visitTypeInterface(t);

        introduceAnonRecordType(cxt, t);
      }
    };
    return converter.convertType(type);
  }

  public IAbstract generateFunction(Variable funPtn, FunctionLiteral fun, CContext cxt)
  {
    CContext funCxt = new CContext(cxt);
    Location loc = fun.getLoc();
    String funName = funPtn.getName();

    pushActionScope();

    for (Variable free : fun.getFreeVars())
      funCxt.defineFree(free.getName(), free.getType(), AccessMode.readOnly);

    final IContentPattern[] args = fun.getArgs();
    List<IAbstract> argPtns = new ArrayList<IAbstract>();
    Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);

    for (int ix = 0; ix < args.length; ix++)
      argPtns.add(compilePtn(args[ix], condition, AccessMode.readOnly, funCxt));

    if (!CompilerUtils.isTrivial(condition.get()))
      errors.reportError("function argument pattern is too complex", fun.getLoc());

    IAbstract exp = generateExp(fun.getBody(), false, funCxt);

    IAbstract funType = typeToAbstract(loc, cxt, fun.getType());

    List<IAbstract> extraActions = getExtraActions();
    if (!extraActions.isEmpty()) {
      extraActions.add(CafeSyntax.valis(loc, exp));
      return CafeSyntax.functionDefn(loc, funName, argPtns, CafeSyntax.valof(loc, CafeSyntax.block(loc, extraActions)),
          funType);
    } else
      return CafeSyntax.functionDefn(loc, funName, argPtns, exp, funType);
  }

  public IAbstract generateLambda(FunctionLiteral fun, CContext cxt)
  {
    CContext funCxt = new CContext(cxt);
    Location loc = fun.getLoc();

    pushActionScope();

    for (Variable free : fun.getFreeVars())
      funCxt.defineFree(free.getName(), free.getType(), AccessMode.readOnly);

    final IContentPattern[] args = fun.getArgs();
    List<IAbstract> argPtns = new ArrayList<IAbstract>();
    Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);

    for (int ix = 0; ix < args.length; ix++)
      argPtns.add(compilePtn(args[ix], condition, AccessMode.readOnly, funCxt));

    if (!CompilerUtils.isTrivial(condition.get()))
      errors.reportError("function argument pattern is too complex", fun.getLoc());

    IAbstract funType = typeToAbstract(loc, cxt, fun.getType());

    IAbstract exp = generateExp(fun.getBody(), false, funCxt);

    List<IAbstract> extraActions = getExtraActions();
    if (!extraActions.isEmpty()) {
      extraActions.add(CafeSyntax.valis(loc, exp));
      return CafeSyntax.lambdaFun(loc, argPtns, CafeSyntax.typeCast(loc, CafeSyntax.valof(loc, CafeSyntax.block(loc,
          extraActions)), CafeSyntax.arrowTypeRes(funType)), funType);
    } else
      return CafeSyntax.lambdaFun(loc, argPtns, CafeSyntax.typeCast(loc, exp, CafeSyntax.arrowTypeRes(funType)),
          funType);
  }

  public IAbstract generateMemoFunction(Variable var, MemoExp memo, CContext cxt)
  {
    CContext funCxt = new CContext(cxt);
    Location loc = memo.getLoc();

    pushActionScope();

    IAbstract exp = generateExp(memo.getMemo(), false, funCxt);

    IAbstract funType = typeToAbstract(loc, cxt, memo.getType());

    List<IAbstract> extraActions = getExtraActions();
    if (!extraActions.isEmpty()) {
      extraActions.add(CafeSyntax.valis(loc, exp));
      exp = CafeSyntax.valof(loc, CafeSyntax.block(loc, extraActions));
    }

    return CafeSyntax.memoDefn(loc, var.getName(), exp, funType);
  }

  public IAbstract generatePtnAbstraction(Variable defPtn, PatternAbstraction def, CContext cxt)
  {
    CContext ptnCxt = new CContext(cxt);
    Location loc = def.getLoc();

    pushActionScope();

    Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);
    IAbstract ptnCode = compilePtn(def.getMatch(), condition, AccessMode.readOnly, ptnCxt);

    if (!CompilerUtils.isTrivial(condition.get()))
      errors.reportError("argument pattern: " + def + " is too complex", loc);

    IAbstract exp = generateExp(def.getResult(), false, ptnCxt);

    List<IAbstract> extraActions = getExtraActions();
    if (!extraActions.isEmpty()) {
      extraActions.add(CafeSyntax.valis(loc, exp));
      exp = CafeSyntax.valof(loc, CafeSyntax.block(loc, extraActions));
      return CafeSyntax.patternDefn(loc, defPtn.getName(), exp, ptnCode, typeToAbstract(loc, cxt, def.getType()));
    } else
      return CafeSyntax.patternDefn(loc, defPtn.getName(), exp, ptnCode, typeToAbstract(loc, cxt, def.getType()));
  }

  private class PtnMatching implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> condition, AccessMode access, CContext cxt)
    {
      MatchingPattern matching = (MatchingPattern) term;
      Variable var = matching.getVar();
      CompilerUtils.extendCondition(condition, new Matches(matching.getLoc(), matching.getVar(), matching.getPtn()));
      return varPattern(var, access, cxt);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return MatchingPattern.class;
    }

  }

  private class PtnOverloadedVariable implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode access, CContext cxt)
    {
      OverloadedVariable var = (OverloadedVariable) term;
      return varPattern(var, access, cxt);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return OverloadedVariable.class;
    }
  }

  private class PtnVariable implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode access, CContext cxt)
    {
      Variable var = (Variable) term;
      return varPattern(var, access, cxt);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return Variable.class;
    }
  }

  private IAbstract varPattern(Variable var, AccessMode access, CContext cxt)
  {
    String name = var.getName();
    cxt.defineLocal(name, var, access);

    Location loc = var.getLoc();
    IAbstract varType = var instanceof OverloadedVariable ? typeToAbstract(loc, cxt, ((OverloadedVariable) var)
        .getDictType()) : typeToAbstract(loc, cxt, var.getType());

    return CafeSyntax.typeCast(loc, Abstract.name(loc, name), varType);
  }

  private class GenerateAbstractionCall implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> condition, AccessMode readOnly, CContext cxt)
    {
      Location loc = term.getLoc();
      PatternApplication apply = (PatternApplication) term;
      IAbstract pttrn = generateScopedExpression(apply.getAbstraction(), true, cxt);
      IAbstract reslt = compilePtn(apply.getArg(), condition, readOnly, cxt);

      if (pttrn instanceof Name)
        return CafeSyntax.callPtn(loc, (Name) pttrn, reslt);
      else {
        Name tmp = introduceVariable(loc, cxt, pttrn, apply.getAbstraction().getType());
        return CafeSyntax.callPtn(loc, tmp, reslt);
      }
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return PatternApplication.class;
    }
  }

  private class PtnAggregate implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
    {
      RecordPtn aggPtn = (RecordPtn) term;
      Location loc = term.getLoc();
      Map<String, Integer> index = aggPtn.getIndex();

      IAbstract args[] = new IAbstract[index.size()];

      for (Entry<String, IContentPattern> entry : aggPtn.getElements().entrySet()) {
        String field = entry.getKey();
        IContentPattern ptn = entry.getValue();
        args[index.get(field)] = compilePtn(ptn, after, readOnly, cxt);
      }

      for (int ix = 0; ix < args.length; ix++)
        if (args[ix] == null)
          args[ix] = CafeSyntax.typeCast(loc, CafeSyntax.anonymous(loc), CafeSyntax.typeVar(loc, GenSym
              .genSym(StandardNames.ANONYMOUS_PREFIX)));

      IType type = aggPtn.getType();

      if (aggPtn.isAnonRecord())
        introduceAnonRecordType(cxt, (TypeInterfaceType) type);

      return CafeSyntax.constructor(loc, generateExp(aggPtn.getFun(), true, cxt), args);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return RecordPtn.class;
    }
  }

  private class PtnTuple implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
    {
      ConstructorPtn posPtn = (ConstructorPtn) term;

      String label = posPtn.getLabel();
      List<IContentPattern> elements = posPtn.getElements();
      List<IAbstract> elPtns = new ArrayList<IAbstract>();

      for (IContentPattern el : elements) {
        if (!(el instanceof Variable || el instanceof Scalar)) {
          Location loc = el.getLoc();
          Variable nVar = new Variable(el.getLoc(), el.getType(), GenSym.genSym("__x"));
          CompilerUtils.extendCondition(after, new Matches(loc, nVar, el));
          elPtns.add(compilePtn(nVar, after, readOnly, cxt));
        } else
          elPtns.add(compilePtn(el, after, readOnly, cxt));
      }

      Location loc = term.getLoc();
      return CafeSyntax.constructor(loc, label, elPtns);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return ConstructorPtn.class;
    }
  }

  private class PtnRegExp implements PatternGenerator
  {
    // A regexp match like
    //
    // `alpha(.*:A)beta`
    //
    // is converted to regexp("alpha(.*)beta")(A)
    //
    // where regexp is a special form understood by Cafe
    //
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
    {
      Location loc = term.getLoc();
      RegExpPattern regExpPtn = (RegExpPattern) term;

      IContentPattern[] groups = regExpPtn.getGroups();
      List<IAbstract> grpPtns = new ArrayList<IAbstract>();

      for (int ix = 0; ix < groups.length; ix++)
        grpPtns.add(compilePtn(groups[ix], after, readOnly, cxt));

      return CafeSyntax.regexp(loc, regExpPtn.getRegexpPtn(), grpPtns);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return RegExpPattern.class;
    }
  }

  private class ScalarPtnGenerator implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern ptn, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
    {
      return abstractValue((ScalarPtn) ptn);
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return ScalarPtn.class;
    }
  }

  private class PtnCast implements PatternGenerator
  {
    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
    {
      CastPtn typeCastPtn = (CastPtn) term;
      IAbstract ptn = compilePtn(typeCastPtn.getInner(), after, readOnly, cxt);
      return CafeSyntax.typeCast(term.getLoc(), ptn, typeToAbstract(term.getLoc(), cxt, term.getType()));
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return CastPtn.class;
    }
  }

  private class PtnWhere implements PatternGenerator
  {

    @Override
    public IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
    {
      WherePattern where = (WherePattern) term;
      IAbstract ptn = compilePtn(where.getPtn(), after, readOnly, cxt);
      CompilerUtils.extendCondition(after, where.getCond());
      return ptn;
    }

    @Override
    public Class<? extends IContentPattern> generator()
    {
      return WherePattern.class;
    }
  }

  private IAbstract compilePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt)
  {
    PatternGenerator compile = patternGenerators.get(term.getClass());
    if (compile != null)
      return compile.generatePtn(term, after, readOnly, cxt);
    else {
      reportError("(internal) missing generator for pattern" + term, term.getLoc());
      return CafeSyntax.voidExp(term.getLoc());
    }
  }

  private class CompileRaiseAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction act, CContext cxt)
    {
      Location loc = act.getLoc();
      return FixedList.create(CafeSyntax.throwExp(loc, generateExp(((RaiseAction) act).getRaised(), false, cxt)));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return RaiseAction.class;
    }
  }

  private class CompileSequence implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      Sequence seq = (Sequence) action;
      final List<IContentAction> actions = seq.getActions();
      final List<IAbstract> acts = new ArrayList<IAbstract>();
      scopeStack.push(acts);

      for (IContentAction act : actions) {
        acts.addAll(compileAction(act, cxt));
      }

      assert scopeStack.peek() == acts;
      scopeStack.pop();
      return acts;
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return Sequence.class;
    }
  }

  private class CompileAssert implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      IAbstract assertion = generateExp(((AssertAction) action).getAssertion(), false, cxt);

      return FixedList.create(CafeSyntax.assertion(action.getLoc(), assertion));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return AssertAction.class;
    }
  }

  private class CompileConditionalAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      ConditionalAction condAction = (ConditionalAction) action;
      Location loc = action.getLoc();
      int mark = cxt.getMark();
      IAbstract test = compileCondition(condAction.getCond(), false, cxt);
      List<IAbstract> then = generateSubAction(condAction.getThPart(), cxt);
      cxt.resetDict(mark);
      List<IAbstract> els = generateSubAction(condAction.getElPart(), cxt);
      return FixedList.create(CafeSyntax.conditional(loc, test, pickOne(loc, then), pickOne(loc, els)));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return ConditionalAction.class;
    }
  }

  private class CompileExceptionHandler implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction act, CContext cxt)
    {
      ExceptionHandler except = (ExceptionHandler) act;
      Location loc = act.getLoc();
      int mark = cxt.getMark();
      List<IAbstract> body = generateSubAction(except.getBody(), cxt);
      cxt.resetDict(mark);

      List<IAbstract> handler = generateSubAction(except.getHandler(), cxt);

      cxt.resetDict(mark);

      return FixedList.create(CafeSyntax.catchAction(loc, pickOne(loc, body), pickOne(loc, handler)));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return ExceptionHandler.class;
    }
  }

  private class CompileIgnored implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      IAbstract ignored = generateExp(((Ignore) action).getIgnored(), false, cxt);

      return FixedList.create(CafeSyntax.ignore(action.getLoc(), ignored));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return Ignore.class;
    }
  }

  private class CompileLoopAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      WhileAction loopAction = (WhileAction) action;
      Location loc = action.getLoc();

      int mark = cxt.getMark();
      IAbstract control = compileCondition(loopAction.getControl(), false, cxt);

      IAbstract loopBody = pickOne(loc, generateSubAction(loopAction.getBody(), cxt));

      cxt.resetDict(mark);
      return FixedList.create(CafeSyntax.whileLoop(loc, control, loopBody));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return WhileAction.class;
    }
  }

  private class CompileSyncAction implements IActionCompiler
  {

    @Override
    public List<IAbstract> compileAct(IContentAction act, CContext cxt)
    {
      SyncAction sync = (SyncAction) act;
      Location loc = sync.getLoc();

      IAbstract sel = generateExp(sync.getSel(), false, cxt);

      if (!(sel instanceof Name)) {
        // introduce a new variable, because cafe sync must have a variable
        sel = introduceVariable(loc, cxt, sel, sync.getSel().getType());
      }

      Map<ICondition, IContentAction> syncCases = sync.getBody();
      if (syncCases.containsKey(CompilerUtils.truth)) {
        IAbstract body = pickOne(loc, generateSubAction(syncCases.get(CompilerUtils.truth), cxt));
        return FixedList.create(CafeSyntax.sync(loc, sel, body));
      } else {
        /**
         * We build this loop:
         * 
         * <pre>
         * <emph>sel</emph> sync {
         *   LL:while true do{
         *     if <emph>cond1</emph> then{
         *       <emph>A1</emph>
         *       leave LL
         *     }
         *     else if ...
         *     else
         *       __wait(<emph>sel</emph>)
         *   }
         *   __notify(sel);
         * }
         * </pre>
         */
        String LL = GenSym.genSym("LL");
        IAbstract leave = CafeSyntax.leave(loc, LL);
        IAbstract body = CafeSyntax.escape(loc, Wait.name, sel);
        for (Entry<ICondition, IContentAction> entry : syncCases.entrySet()) {
          IAbstract caseCond = compileCondition(entry.getKey(), false, cxt);
          IAbstract caseBody = pickOne(loc, mergeActions(generateSubAction(entry.getValue(), cxt), leave));
          body = CafeSyntax.conditional(loc, caseCond, caseBody, body);
        }
        IAbstract whileStmt = CafeSyntax.whileLoop(loc, CafeSyntax.truth(loc), body);
        whileStmt = CafeSyntax.labeled(loc, LL, whileStmt);
        IAbstract syncBody = CafeSyntax.block(loc, whileStmt, CafeSyntax.escape(loc, Notify.name, sel));
        return FixedList.create(CafeSyntax.sync(loc, sel, syncBody));
      }
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return SyncAction.class;
    }
  }

  private class CompileValisAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      ValisAction valis = (ValisAction) action;
      Location loc = action.getLoc();

      IContentExpression value = valis.getValue();
      if (value instanceof ValofExp)
        return compileAction(((ValofExp) value).getAction(), cxt);
      else
        return FixedList.create(CafeSyntax.valis(loc, generateExp(value, false, cxt)));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return ValisAction.class;
    }
  }

  private class CompileProcedureCallAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      Location loc = action.getLoc();
      ProcedureCallAction call = (ProcedureCallAction) action;
      IContentExpression proc = call.getProc();
      assert proc instanceof Variable && JavaImport.isJavaName(((Variable) proc).getName());

      IAbstract[] args = compileExps(call.getArgs(), cxt);

      return FixedList.create(CafeSyntax.escape(loc, ((Variable) proc).getName(), args));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return ProcedureCallAction.class;
    }
  }

  private class CompileLetAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      final LetAction let = (LetAction) action;
      final Location loc = action.getLoc();
      final IContentAction bound = let.getBoundAction();

      BoundCompiler compileBound = new BoundCompiler() {

        @Override
        public IAbstract compileBound(List<IAbstract> definitions, List<IAbstract> doActions, CContext thetaCxt)
        {
          IAbstract boundAct = pickOne(bound.getLoc(), generateSubAction(bound, thetaCxt));
          return CafeSyntax.letExp(loc, definitions, boundAct);
        }
      };

      return FixedList.create(compileThetaEnv(let.getEnvironment(), compileBound, cxt));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return LetAction.class;
    }
  }

  private class CompileNullAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      return FixedList.create();
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return NullAction.class;
    }
  }

  private static IAbstract pickOne(Location loc, List<IAbstract> els)
  {
    if (els.size() == 1)
      return els.get(0);
    else if (els.isEmpty())
      return CafeSyntax.nothing(loc);
    else
      return CafeSyntax.block(loc, els);
  }

  private static List<IAbstract> mergeActions(List<IAbstract> lhs, List<IAbstract> rhs)
  {
    if (lhs.isEmpty())
      return rhs;
    else if (rhs.isEmpty())
      return lhs;
    else {
      IAbstract els[] = new IAbstract[lhs.size() + rhs.size()];
      int ix = 0;
      for (IAbstract el : lhs)
        els[ix++] = el;
      for (IAbstract el : rhs)
        els[ix++] = el;
      return FixedList.create(els);
    }
  }

  private static List<IAbstract> mergeActions(List<IAbstract> lhs, IAbstract rhs)
  {
    IAbstract els[] = new IAbstract[lhs.size() + 1];
    int ix = 0;
    for (IAbstract el : lhs)
      els[ix++] = el;
    els[ix] = rhs;
    return FixedList.create(els);
  }

  private class CompileVarDeclaration implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      VarDeclaration decl = (VarDeclaration) action;
      return compileVarDeclaration(decl.getLoc(), decl.getPattern(), decl.isReadOnly(), decl.getValue(), cxt);
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return VarDeclaration.class;
    }
  }

  private class CompileYield implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      List<IAbstract> yielded = compileAction(((Yield) action).getYielded(), cxt);
      Location loc = action.getLoc();
      return FixedList.create(CafeSyntax.yieldAction(loc, pickOne(loc, yielded)));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return Yield.class;
    }
  }

  /*
   * This really is a temporary solution: we should be generating the var declarations directly,
   * rather than reconstructing them from the pattern.
   */
  private List<IAbstract> generateConditionVarDeclarations(Location loc, ICondition cond, AccessMode access,
      CContext cxt)
  {
    if (cond instanceof Conjunction) {
      Conjunction conj = (Conjunction) cond;
      List<IAbstract> lhs = generateConditionVarDeclarations(loc, conj.getLhs(), access, cxt);
      List<IAbstract> rhs = generateConditionVarDeclarations(loc, conj.getRhs(), access, cxt);
      return mergeActions(lhs, rhs);
    } else if (cond instanceof Matches) {
      Matches match = (Matches) cond;
      return compileVarDeclaration(loc, match.getPtn(), access, match.getExp(), cxt);
    } else {
      errors.reportError("unknown condition in variable declaration pattern: " + cond, cond.getLoc());
      return null;
    }
  }

  private List<IAbstract> compileVarDeclaration(Location loc, IContentPattern lval, AccessMode access,
      IContentExpression exp, CContext cxt)
  {
    IAbstract init = generateExp(exp, false, cxt);
    Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);
    IAbstract ptn = compilePtn(lval, condition, access, cxt);

    final List<IAbstract> res = new ArrayList<IAbstract>();
    switch (access) {
    case readOnly:
      res.add(CafeSyntax.isDeclaration(loc, ptn, init));
      break;
    default:
      res.add(CafeSyntax.varDeclaration(loc, ptn, init));
    }

    ICondition cond = condition.get();
    if (CompilerUtils.isTrivial(cond))
      return res;

    res.addAll(generateConditionVarDeclarations(loc, cond, access, cxt));
    return res;
  }

  private class CompileAssignment implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      Assignment assign = (Assignment) action;

      IContentExpression lValue = assign.getLValue();
      IAbstract a1 = generateExp(lValue, false, cxt);
      IContentExpression value = assign.getValue();
      IAbstract a2 = generateExp(value, false, cxt);
      Location loc = action.getLoc();

      IType argType = value.getType();
      return FixedList.create(assignment(loc, a1, a2, argType));
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return Assignment.class;
    }
  }

  private static IAbstract assignment(Location loc, IAbstract lval, IAbstract rval, IType type)
  {
    if (TypeUtils.isRawBoolType(type))
      return CafeSyntax.escape(loc, AssignRawBool.name, lval, rval);
    else if (TypeUtils.isRawCharType(type))
      return CafeSyntax.escape(loc, AssignRawChar.name, lval, rval);
    else if (TypeUtils.isRawIntType(type))
      return CafeSyntax.escape(loc, AssignRawInteger.name, lval, rval);
    else if (TypeUtils.isRawLongType(type))
      return CafeSyntax.escape(loc, AssignRawLong.name, lval, rval);
    else if (TypeUtils.isRawFloatType(type))
      return CafeSyntax.escape(loc, AssignRawFloat.name, lval, rval);
    else if (CafeSyntax.isTuple(lval))
      return CafeSyntax.assignment(loc, lval, rval);
    else
      return CafeSyntax.escape(loc, Assign.name, lval, rval);
  }

  /**
   * A case action that analyses algebraic types:
   * 
   * <pre>
   * case Exp in {
   *   foo(X1,X2) do A1;
   *   bar(X3) do A2;
   * }
   * </pre>
   * 
   * is compiled to
   * 
   * <pre>
   * (switch <Exp> (
   *   ((foo <X1> <X2>) <A1>)
   *   ((bar <X3>) A2))
   *   <deflt>
   * )
   * </pre>
   */
  private class CompileCaseAction implements IActionCompiler
  {
    @Override
    public List<IAbstract> compileAct(IContentAction action, CContext cxt)
    {
      CaseAction caseAct = (CaseAction) action;
      List<IAbstract> cases = generateCases(caseAct.getCases(), cxt);

      IContentAction deflt = caseAct.getDeflt();
      Location defltLoc = deflt.getLoc();
      List<IAbstract> defCase = generateSubAction(deflt, cxt);

      IAbstract selector = generateExp(caseAct.getSelector(), true, cxt);

      if (selector instanceof Name)
        return FixedList.create(CafeSyntax.switchAction(caseAct.getLoc(), selector, cases, pickOne(defltLoc, defCase)));
      else {
        // introduce a new variable
        Name tmp = introduceVariable(selector.getLoc(), cxt, selector, caseAct.getSelector().getType());
        return FixedList.create(CafeSyntax.switchAction(caseAct.getLoc(), tmp, cases, pickOne(defltLoc, defCase)));
      }
    }

    @Override
    public Class<? extends IContentAction> actionClass()
    {
      return CaseAction.class;
    }
  }

  private List<IAbstract> generateCases(List<Pair<IContentPattern, IContentAction>> arms, CContext cxt)
  {
    List<IAbstract> cases = new ArrayList<IAbstract>();

    for (Pair<IContentPattern, IContentAction> entry : arms) {
      IContentPattern ptn = entry.getKey();
      IContentAction body = entry.getValue();
      Location loc = body.getLoc();

      if (ptn instanceof Variable)
        reportError("variable not permitted in case: " + ptn, ptn.getLoc());
      else if (ptn instanceof ConstructorPtn || ptn instanceof RecordPtn || ptn instanceof ScalarPtn) {
        int mark = cxt.getMark();
        Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);
        IAbstract ptnCode = compilePtn(ptn, condition, AccessMode.readOnly, cxt);
        if (!CompilerUtils.isTrivial(condition.get()))
          errors.reportError("pattern " + ptn + " too complex", ptn.getLoc());

        List<IAbstract> code = generateSubAction(body, cxt);

        cases.add(CafeSyntax.caseRule(ptn.getLoc(), ptnCode, pickOne(loc, code)));

        cxt.resetDict(mark);
      } else if (ptn != null)
        reportError("invalid pattern in case: " + ptn, ptn.getLoc());
    }
    return cases;
  }

  private List<IAbstract> compileAction(IContentAction action, CContext cxt)
  {
    IActionCompiler compile = actionGenerators.get(action.getClass());
    if (compile != null)
      return compile.compileAct(action, cxt);
    else {
      reportError("(internal) missing generator for action " + action, action.getLoc());

      return FixedList.create(); // Default case
    }
  }

  private List<IAbstract> generateSubAction(IContentAction action, CContext cxt)
  {
    pushActionScope();

    List<IAbstract> code = compileAction(action, cxt);

    List<IAbstract> condActions = getExtraActions();

    return mergeActions(condActions, code);
  }

  public IAbstract abstractValue(ScalarPtn lit)
  {
    return AbstractValue.abstractValue(lit.getLoc(), lit.getValue(), errors);
  }

  public IAbstract abstractValue(Scalar lit)
  {
    return AbstractValue.abstractValue(lit.getLoc(), lit.getValue(), errors);
  }

  private class CompileVariable implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression term, boolean isDeep, CContext cxt)
    {
      assert term instanceof Variable;
      Variable var = (Variable) term;
      Location loc = var.getLoc();
      String name = var.getName();

      IContentExpression rewrite = cxt.rewriteVar(name);

      if (rewrite instanceof Variable)
        return varReference(loc, ((Variable) rewrite).getName(), cxt, errors);
      else
        return varReference(loc, name, cxt, errors);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return Variable.class;
    }
  }

  private static String findThisVariable(final String name, CContext cxt)
  {
    final Wrapper<String> thisName = Wrapper.create(null);
    cxt.visitCContext(new EntryVisitor<String, DictEntry>() {

      @Override
      public ContinueFlag visit(String key, DictEntry var)
      {
        IType type = TypeUtils.unwrap(var.getType());
        if (type instanceof TypeInterfaceType) {
          if (((TypeInterfaceType) type).getAllFields().containsKey(name)) {
            thisName.set(var.getName());
            return ContinueFlag.stop;
          }
        }
        return ContinueFlag.cont;
      }
    });
    return thisName.get();
  }

  private static IAbstract varReference(Location loc, String varName, CContext cxt, ErrorReport errors)
  {
    IAbstract txVar = CafeSyntax.variable(loc, varName);
    DictEntry info = cxt.getDictInfo(varName);
    if (info != null) {
      if (info.getAccess() == AccessMode.readWrite && info.getBindingKind() == BindingKind.free) {
        String thisVar = findThisVariable(varName, cxt);
        if (thisVar != null)
          return CafeSyntax.dot(loc, CafeSyntax.variable(loc, thisVar), varName);
        else
          return txVar;
      }
    }

    return txVar;
  }

  private class CompileShriek implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression term, boolean isDeep, CContext cxt)
    {
      assert term instanceof Shriek;
      Shriek ref = (Shriek) term;

      Location loc = term.getLoc();

      IAbstract arg = generateExp(ref.getReference(), true, cxt);
      IType argType = ref.getReference().getType();
      assert TypeUtils.isReferenceType(argType);
      if (TypeUtils.isRawBoolType(TypeUtils.referencedType(argType)))
        return CafeSyntax.escape(loc, GetRawBoolRef.name, arg);
      else if (TypeUtils.isRawCharType(TypeUtils.referencedType(argType)))
        return CafeSyntax.escape(loc, GetRawCharRef.name, arg);
      else if (TypeUtils.isRawIntType(TypeUtils.referencedType(argType)))
        return CafeSyntax.escape(loc, GetRawIntegerRef.name, arg);
      else if (TypeUtils.isRawLongType(TypeUtils.referencedType(argType)))
        return CafeSyntax.escape(loc, GetRawLongRef.name, arg);
      else if (TypeUtils.isRawFloatType(TypeUtils.referencedType(argType)))
        return CafeSyntax.escape(loc, GetRawFloatRef.name, arg);
      else
        return CafeSyntax.escape(loc, GetRef.name, arg);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return Shriek.class;
    }
  }

  private class CompileMemo implements ExpressionGenerator
  {

    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      MemoExp memo = (MemoExp) exp;
      Location loc = memo.getLoc();

      // We form a theta environment of the memo function. Similar to a anonymous function
      String name = GenSym.genSym("memo$");
      Variable memoVar = new Variable(loc, exp.getType(), name);

      List<IAbstract> thetaDefs = new ArrayList<IAbstract>();
      thetaDefs.add(generateMemoFunction(memoVar, memo, cxt));

      return CafeSyntax.letExp(loc, thetaDefs, CafeSyntax.variable(loc, name));
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return MemoExp.class;
    }
  }

  private class CompileOverloadedVariable implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      reportError("unresolved variable: " + exp, exp.getLoc());

      return CafeSyntax.voidExp(exp.getLoc());
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return OverloadedVariable.class;
    }
  }

  private class CompileOverloadedFieldAccess implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      reportError("unresolved variable: " + exp, exp.getLoc());

      return CafeSyntax.voidExp(exp.getLoc());
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return OverloadedFieldAccess.class;
    }
  }

  private class CompileMethodVariable implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      MethodVariable mVar = (MethodVariable) exp;

      ExpressionGenerator conCompiler = DefaultContracts.getDefaultContract(mVar.getContractName());
      if (conCompiler != null)
        return conCompiler.generateExpression(exp, isDeep, cxt);
      else {
        reportError("unresolved method: " + exp, exp.getLoc());

        return CafeSyntax.voidExp(exp.getLoc());
      }
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return MethodVariable.class;
    }
  }

  private class GenerateApplication implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      Application funApp = (Application) exp;
      IContentExpression applied = funApp.getFunction();
      IAbstract fun = generateExp(applied, isDeep, cxt);
      IAbstract args = generateExp(funApp.getArgs(), false, cxt);

      Location loc = exp.getLoc();

      if (TypeUtils.isFunType(applied.getType())) {
        if (fun instanceof Name)
          return CafeSyntax.funcall(loc, fun, args);
        else {
          // introduce a new variable
          Name tmp = introduceVariable(loc, cxt, fun, applied.getType());
          return CafeSyntax.funcall(loc, tmp, args);
        }
      } else {
        assert TypeUtils.isConstructorType(applied.getType());
        assert CafeSyntax.isTuple(args);
        if (fun instanceof Name)
          return CafeSyntax.constructor(loc, fun, CafeSyntax.constructorArgs(args));
        else {
          Name tmp = introduceVariable(loc, cxt, fun, applied.getType());
          return CafeSyntax.constructor(loc, tmp, CafeSyntax.constructorArgs(args));
        }
      }
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return Application.class;
    }
  }

  private void introduceDecl(IAbstract decl)
  {
    List<IAbstract> introDefs = scopeStack.peek();
    introDefs.add(decl);
  }

  private Name introduceVariable(Location loc, CContext cxt, IAbstract val, IType type)
  {
    IAbstract abType = typeToAbstract(loc, cxt, type);

    Name tmp = CafeSyntax.variable(loc, GenSym.genSym("$V"));

    IAbstract lVal = CafeSyntax.typeCast(loc, tmp, abType);
    IAbstract decl = CafeSyntax.isDeclaration(loc, lVal, val);
    introduceDecl(decl);
    return tmp;
  }

  private void pushActionScope()
  {
    List<IAbstract> extraActions = new ArrayList<IAbstract>();
    scopeStack.push(extraActions);
  }

  private List<IAbstract> getExtraActions()
  {
    return scopeStack.pop();
  }

  private class CompileFieldAccess implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      FieldAccess dot = (FieldAccess) exp;
      Location loc = exp.getLoc();
      IContentExpression route = dot.getRecord();
      IAbstract rc = generateExp(route, isDeep, cxt);

      if (rc instanceof Name)
        return CafeSyntax.dot(loc, rc, dot.getField());
      else {
        IAbstract tmp = introduceVariable(loc, cxt, rc, route.getType());
        return CafeSyntax.dot(loc, tmp, dot.getField());
      }
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return FieldAccess.class;
    }
  }

  private class CompilerResolved implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      Resolved res = (Resolved) exp;
      IContentExpression dictVars = new ConstructorTerm(exp.getLoc(), res.getDicts());

      IAbstract dicts = generateExp(dictVars, false, cxt);

      IContentExpression over = res.getOver();
      IAbstract fun = generateExp(over, isDeep, cxt);
      Location loc = exp.getLoc();

      if (fun instanceof Name)
        return CafeSyntax.funcall(loc, fun, dicts);
      else {
        // introduce a new variable
        Name tmp = introduceVariable(loc, cxt, fun, over.getType());
        return CafeSyntax.funcall(loc, tmp, dicts);
      }
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return Resolved.class;
    }
  }

  private class CompileScalar implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      return AbstractValue.abstractValue(exp.getLoc(), ((Scalar) exp).getValue(), errors);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return Scalar.class;
    }
  }

  private class CompileTuple implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      ConstructorTerm posLit = (ConstructorTerm) exp;
      Location loc = exp.getLoc();

      List<IAbstract> elExps = compileExps(posLit.getElements(), cxt);
      String label = posLit.getLabel();

      return makeTuple(cxt, exp.getType(), label, loc, elExps);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return ConstructorTerm.class;
    }
  }

  private class CompileNullExp implements ExpressionGenerator
  {

    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      return CafeSyntax.nullPtn(exp.getLoc());
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return NullExp.class;
    }
  }

  private class GenerateRecord implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      RecordTerm record = (RecordTerm) exp;

      IAbstract fun = generateExp(record.getFun(), isDeep, cxt);
      SortedMap<String, IContentExpression> aggEls = record.getArguments();

      IAbstract[] args = new IAbstract[aggEls.size()];

      int offset = 0;
      for (Entry<String, IContentExpression> entry : aggEls.entrySet()) {
        args[offset++] = generateExp(entry.getValue(), true, cxt);
      }

      Location loc = exp.getLoc();

      IType funType = record.getFun().getType();

      if (TypeUtils.isFunType(funType)) {
        if (fun instanceof Name)
          return CafeSyntax.funcall(loc, fun, CafeSyntax.tuple(loc, fun, args));
        else {
          // introduce a new variable
          Name tmp = introduceVariable(loc, cxt, fun, funType);
          return CafeSyntax.funcall(loc, tmp, CafeSyntax.tuple(loc, fun, args));
        }
      } else {
        assert TypeUtils.isConstructorType(funType);

        if (fun instanceof Name)
          return CafeSyntax.constructor(loc, fun, args);
        else {
          Name tmp = introduceVariable(loc, cxt, fun, funType);
          return CafeSyntax.constructor(loc, tmp, args);
        }
      }
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return RecordTerm.class;
    }
  }

  private class GenerateSubstitute implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      Location loc = exp.getLoc();
      RecordSubstitute update = (RecordSubstitute) exp;
      List<IAbstract> valActions = new ArrayList<IAbstract>();

      IContentExpression route = update.getRoute();
      IAbstract rc = generateExp(route, isDeep, cxt);
      IContentExpression replace = update.getReplace();

      String tmpName = GenSym.genSym("__copy");
      IAbstract tmp = Abstract.name(loc, tmpName);
      valActions.add(CafeSyntax.varDeclaration(loc, CafeSyntax.typeCast(loc, tmp, typeToAbstract(loc, cxt, route
          .getType())), CafeSyntax.copy(loc, rc)));

      if (replace instanceof RecordTerm) {
        RecordTerm record = (RecordTerm) replace;
        for (Entry<String, IContentExpression> f : record.getArguments().entrySet()) {
          String fld = f.getKey();
          IContentExpression fldVal = f.getValue();

          valActions.add(CafeSyntax.assignment(loc, CafeSyntax.dot(loc, tmp, fld), generateExp(fldVal, isDeep, cxt)));
        }
      } else {
        IAbstract repl = generateExp(replace, isDeep, cxt);
        IAbstract repVar = Abstract.name(loc, GenSym.genSym("__repl"));
        valActions.add(CafeSyntax.isDeclaration(loc, CafeSyntax.typeCast(loc, repVar, typeToAbstract(loc, cxt, replace
            .getType())), repl));

        TypeInterfaceType replType = (TypeInterfaceType) TypeUtils.deRef(replace.getType());

        introduceAnonRecordType(cxt, replType);

        Map<String, IType> replMembers = replType.getAllFields();

        for (Entry<String, IType> entry : replMembers.entrySet()) {
          String att = entry.getKey();
          valActions.add(CafeSyntax.assignment(loc, CafeSyntax.dot(loc, tmp, att), CafeSyntax.dot(loc, repVar, att)));
        }
      }

      valActions.add(CafeSyntax.valis(loc, tmp));

      return CafeSyntax.valof(loc, CafeSyntax.block(loc, valActions));
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return RecordSubstitute.class;
    }
  }

  private class CompileConditionalExp implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      ConditionalExp conditional = (ConditionalExp) exp;
      Location loc = exp.getLoc();
      int mark = cxt.getMark();

      pushActionScope();
      IAbstract tst = compileCondition(conditional.getCnd(), isDeep, cxt);
      IContentExpression thExp = conditional.getThExp();

      IAbstract then = generateValis(thExp, cxt);

      cxt.resetDict(mark);
      IAbstract els = generateValis(conditional.getElExp(), cxt);
      cxt.resetDict(mark);

      List<IAbstract> condActions = getExtraActions();

      IAbstract condAction = CafeSyntax.conditional(loc, tst, then, els);
      condActions.add(condAction);
      return CafeSyntax.valof(loc, CafeSyntax.block(loc, condActions));
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return ConditionalExp.class;
    }
  }

  private class CompileValof implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      ValofExp valof = (ValofExp) exp;
      IContentAction valAction = valof.getAction();
      int mark = cxt.getMark();

      pushActionScope();

      List<IAbstract> action = compileAction(valAction, cxt);

      cxt.resetDict(mark);

      List<IAbstract> extra = getExtraActions();

      Location loc = exp.getLoc();
      if (action.size() == 1 && CafeSyntax.isValis(action.get(0)) && extra.isEmpty())
        return CafeSyntax.valisExp(pickOne(loc, action));
      else if (!extra.isEmpty()) {
        extra.addAll(action);
        return CafeSyntax.valof(loc, CafeSyntax.block(loc, extra));
      } else
        return CafeSyntax.valof(loc, pickOne(loc, action));
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return ValofExp.class;
    }
  }

  private class GenerateCaseExp implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      CaseExpression caseExp = (CaseExpression) exp;
      List<IAbstract> cases = new ArrayList<IAbstract>();

      for (Pair<IContentPattern, IContentExpression> entry : caseExp.getCases()) {
        IContentPattern ptn = entry.getKey();
        IContentExpression caseVal = entry.getValue();

        if (ptn instanceof Variable)
          reportError("variable not permitted in case: " + ptn, ptn.getLoc());
        else if (ptn instanceof ConstructorPtn || ptn instanceof RecordPtn) {
          int mark = cxt.getMark();
          Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);
          IAbstract ptnCode = compilePtn(ptn, condition, AccessMode.readOnly, cxt);
          if (!CompilerUtils.isTrivial(condition.get()))
            errors.reportError("pattern " + ptn + " too complex", ptn.getLoc());

          IAbstract code = generateScopedExpression(caseVal, isDeep, cxt);
          cases.add(CafeSyntax.caseRule(ptn.getLoc(), ptnCode, code));
          cxt.resetDict(mark);
        } else if (ptn instanceof ScalarPtn) {
          ScalarPtn key = (ScalarPtn) ptn;
          IContentExpression body = entry.getValue();
          IAbstract code = generateScopedExpression(body, isDeep, cxt);
          cases.add(CafeSyntax.caseRule(key.getLoc(), abstractValue(key), code));
        } else if (ptn != null)
          reportError("invalid pattern in case: " + ptn, ptn.getLoc());
      }

      IAbstract defCase;

      if (caseExp.getDeflt() != null)
        defCase = generateScopedExpression(caseExp.getDeflt(), isDeep, cxt);
      else
        defCase = CafeSyntax.throwExp(exp.getLoc(), Abstract.newString(exp.getLoc(), "switch failed"));

      IAbstract selector = generateExp(caseExp.getSelector(), isDeep, cxt);

      if (selector instanceof Name)
        return CafeSyntax.switchAction(caseExp.getLoc(), selector, cases, defCase);
      else {
        // introduce a new variable
        Name tmp = introduceVariable(selector.getLoc(), cxt, selector, caseExp.getSelector().getType());
        return CafeSyntax.switchAction(caseExp.getLoc(), tmp, cases, defCase);
      }
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return CaseExpression.class;
    }
  }

  private class CompileLetTerm implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(final IContentExpression exp, final boolean isDeep, CContext cxt)
    {
      final LetTerm let = (LetTerm) exp;
      final IContentExpression bound = let.getBoundExp();

      BoundCompiler compileBound = new BoundCompiler() {

        @Override
        public IAbstract compileBound(List<IAbstract> definitions, List<IAbstract> doActions, CContext thetaCxt)
        {
          Location loc = bound.getLoc();
          scopeStack.push(doActions);
          IAbstract boundExp = generateExp(bound, isDeep, thetaCxt);
          scopeStack.pop();
          if (!doActions.isEmpty()) {
            doActions.add(CafeSyntax.valis(loc, boundExp));
            boundExp = CafeSyntax.valof(loc, CafeSyntax.block(loc, doActions));
          }
          return CafeSyntax.letExp(exp.getLoc(), definitions, boundExp);
        }
      };

      return compileThetaEnv(let.getEnvironment(), compileBound, cxt);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return LetTerm.class;
    }
  }

  private class CompileFunctionLiteral implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(final IContentExpression exp, boolean isDeep, CContext cxt)
    {
      return generateLambda((FunctionLiteral) exp, cxt);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return FunctionLiteral.class;
    }
  }

  private class CompilePatternAbstraction implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(final IContentExpression exp, boolean isDeep, CContext cxt)
    {
      PatternAbstraction ptnAbstr = (PatternAbstraction) exp;
      Location loc = exp.getLoc();

      Variable defVar = new Variable(ptnAbstr.getLoc(), ptnAbstr.getType(), ptnAbstr.getName());
      IAbstract def = generatePtnAbstraction(defVar, ptnAbstr, cxt);
      List<IAbstract> definitions = FixedList.create(def);

      return CafeSyntax.letExp(exp.getLoc(), definitions, new Name(loc, defVar.getName()));
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return PatternAbstraction.class;
    }
  }

  private class CompileCast implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      CastExpression cast = (CastExpression) exp;
      Location loc = exp.getLoc();
      IAbstract val = generateExp(cast.getInner(), isDeep, cxt);
      IAbstract type = typeToAbstract(loc, cxt, cast.getType());
      return CafeSyntax.typeCast(loc, val, type);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return CastExpression.class;
    }
  }

  private class CompileRaise implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(final IContentExpression exp, boolean isDeep, CContext cxt)
    {
      Location loc = exp.getLoc();
      return CafeSyntax.throwExp(loc, generateExp(((RaiseExpression) exp).getRaise(), isDeep, cxt));
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return RaiseExpression.class;
    }
  }

  private class CompileVoid implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      return unit(cxt, exp.getLoc());
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return VoidExp.class;
    }
  }

  private class CompileContentCondition implements ExpressionGenerator
  {
    @Override
    public IAbstract generateExpression(IContentExpression exp, boolean isDeep, CContext cxt)
    {
      return compileCondition(((ContentCondition) exp).getCondition(), isDeep, cxt);
    }

    @Override
    public Class<? extends IContentExpression> expressionClass()
    {
      return ContentCondition.class;
    }
  }

  public IAbstract generateExp(IContentExpression exp, boolean isDeep, CContext cxt)
  {
    final Location loc = exp.getLoc();

    ExpressionGenerator compile = expressionGenerators.get(exp.getClass());
    if (compile != null) {
      IAbstract generated = compile.generateExpression(exp, isDeep, cxt);

      if (isDeep && !isSimple(generated))
        return introduceVariable(generated.getLoc(), cxt, generated, exp.getType());

      return generated;
    } else
      reportError("(internal) missing generator for expression " + exp, loc);
    return null;
  }

  private static boolean isSimple(IAbstract exp)
  {
    return !(exp instanceof Apply) || CafeSyntax.isThrow(exp) || CafeSyntax.isLetExp(exp);
  }

  private IAbstract generateScopedExpression(IContentExpression exp, boolean isDeep, CContext cxt)
  {
    pushActionScope();

    IAbstract code = generateExp(exp, isDeep, cxt);

    List<IAbstract> condActions = getExtraActions();

    if (!condActions.isEmpty()) {
      Location loc = exp.getLoc();
      condActions.add(CafeSyntax.valis(loc, code));
      return CafeSyntax.valof(loc, CafeSyntax.block(loc, condActions));
    } else
      return code;
  }

  private IAbstract generateValis(IContentExpression exp, CContext cxt)
  {
    IAbstract generated = generateScopedExpression(exp, false, cxt);
    if (CafeSyntax.isValof(generated))
      return CafeSyntax.valofAction(generated);
    else
      return CafeSyntax.valis(exp.getLoc(), generated);
  }

  private IAbstract compileCondition(ICondition query, boolean isDeep, CContext cxt)
  {
    VarChecker checker = new VarChecker() {

      @Override
      public boolean isThisOk(Variable var)
      {
        return true;
      }
    };
    query = FlowAnalysis.analyseFlow(query, cxt.definedVariables(), checker);
    return compCond(query, isDeep, cxt);
  }

  private IAbstract compCond(ICondition query, boolean isDeep, CContext cxt)
  {
    final Location loc = query.getLoc();
    if (query instanceof Conjunction) {
      Conjunction con = (Conjunction) query;
      IAbstract left = compCond(con.getLhs(), isDeep, cxt);
      IAbstract right = compCond(con.getRhs(), isDeep, cxt);
      return CafeSyntax.conjunction(loc, left, right);
    } else if (query instanceof Disjunction) {
      Disjunction disj = (Disjunction) query;
      int mark = cxt.getMark();
      IAbstract left = compCond(disj.getLhs(), isDeep, cxt);
      cxt.resetDict(mark);
      IAbstract right = compCond(disj.getRhs(), isDeep, cxt);
      return CafeSyntax.disjunction(loc, left, right);
    } else if (query instanceof Negation) {
      Negation neg = (Negation) query;
      IAbstract rhs = compCond(neg.getNegated(), isDeep, cxt);
      return CafeSyntax.negation(loc, rhs);
    } else if (query instanceof Search) {
      reportError("(internal) predication " + query + " should have been rewritten", loc);
      return CafeSyntax.falseness(loc);
    } else if (query instanceof Otherwise) {
      Otherwise other = (Otherwise) query;
      IAbstract left = compCond(other.getLhs(), isDeep, cxt);
      IAbstract rgt = compCond(other.getRhs(), isDeep, cxt);
      if (CafeSyntax.isTruth(left))
        return left;
      else if (CafeSyntax.isFalse(left))
        return rgt;
      else
        return CafeSyntax.conditional(loc, left, CafeSyntax.truth(loc), rgt);
    } else if (query instanceof TrueCondition)
      return CafeSyntax.truth(loc);
    else if (query instanceof FalseCondition)
      return CafeSyntax.falseness(loc);
    else if (query instanceof Variable)
      return generateExp((Variable) query, isDeep, cxt);
    else if (query instanceof Matches) {
      Matches matches = (Matches) query;

      Wrapper<ICondition> condition = Wrapper.create(CompilerUtils.truth);
      IAbstract lhs = generateScopedExpression(matches.getExp(), false, cxt);
      IAbstract rhs = compilePtn(matches.getPtn(), condition, AccessMode.readOnly, cxt);
      ICondition cond = condition.get();
      if (CompilerUtils.isTrivial(cond))
        return CafeSyntax.match(loc, lhs, rhs);
      else
        return CafeSyntax.conjunction(loc, CafeSyntax.match(loc, lhs, rhs), compCond(cond, isDeep, cxt));
    } else if (query instanceof IsTrue)
      return generateScopedExpression(((IsTrue) query).getExp(), isDeep, cxt);
    else if (query instanceof ConditionCondition) {
      ConditionCondition cond = (ConditionCondition) query;
      IAbstract lhs = compCond(cond.getLhs(), true, cxt);
      IAbstract rhs = compCond(cond.getRhs(), true, cxt);
      return CafeSyntax.conditional(loc, compCond(cond.getTest(), true, cxt), lhs, rhs);
    } else {
      reportError("cannot compile condition: " + query, loc);
      return CafeSyntax.falseness(loc);
    }
  }

  private List<IAbstract> compileExps(List<IContentExpression> args, CContext cxt)
  {
    List<IAbstract> exps = new ArrayList<IAbstract>();
    for (IContentExpression arg : args)
      exps.add(generateExp(arg, true, cxt));
    return exps;
  }

  private IAbstract[] compileExps(IContentExpression args[], CContext cxt)
  {
    IAbstract exps[] = new IAbstract[args.length];
    for (int ix = 0; ix < args.length; ix++)
      exps[ix] = generateExp(args[ix], true, cxt);
    return exps;
  }

  private interface BoundCompiler
  {
    IAbstract compileBound(List<IAbstract> definitions, List<IAbstract> doActions, CContext boundCxt);
  }

  private IAbstract compileThetaEnv(List<IStatement> env, BoundCompiler bound, CContext cxt)
  {
    int mark = cxt.getMark();

    List<IAbstract> thetaIns = new ArrayList<IAbstract>();
    List<IAbstract> doActions = new ArrayList<IAbstract>();
    Set<String> types = new HashSet<String>();

    for (IStatement stmt : env) {
      Location loc = stmt.getLoc();
      scopeStack.push(thetaIns);

      if (stmt instanceof VarEntry) {
        VarEntry vEntry = (VarEntry) stmt;

        IContentPattern defnPtn = vEntry.getVarPattern();

        IContentExpression value = vEntry.getValue();
        if (value instanceof FunctionLiteral)
          thetaIns.add(generateFunction((Variable) defnPtn, (FunctionLiteral) value, cxt));
        else if (value instanceof PatternAbstraction)
          thetaIns.add(generatePtnAbstraction((Variable) defnPtn, (PatternAbstraction) value, cxt));
        else if (value instanceof MemoExp)
          thetaIns.add(generateMemoFunction(vEntry.getVariable(), (MemoExp) value, cxt));
        else {
          AccessMode access = vEntry.isReadOnly();

          if (defnPtn instanceof Variable)
            thetaIns.addAll(compileVarDeclaration(loc, defnPtn, access, value, cxt));
          else if (defnPtn instanceof ConstructorPtn && value instanceof ConstructorTerm) {
            List<IContentPattern> ptnEls = ((ConstructorPtn) defnPtn).getElements();
            List<IContentExpression> valEls = ((ConstructorTerm) value).getElements();
            if (ptnEls.size() != valEls.size())
              errors.reportError("inconsistent sizes in variable definition", stmt.getLoc());
            else {
              /**
               * A definition of the form
               * 
               * <pre>
               * (v1,..,vn) = (e1,..,en)
               * </pre>
               * 
               * is broken up because ei may reference vj We therefore generate the equivalent of:
               * 
               * <pre>
               * (v1,..vn) = let{
               *  v1=e1; .. vn=en
               * } in (v1,..,vn)
               * </pre>
               */
              List<IAbstract> localDefs = new ArrayList<IAbstract>();
              for (int ix = 0; ix < ptnEls.size(); ix++) {
                IContentPattern ptn = ptnEls.get(ix);
                IContentExpression val = valEls.get(ix);
                localDefs.addAll(compileVarDeclaration(loc, ptn, access, val, cxt));
              }
              Pair<IAbstract, IAbstract> args = findVarsInDefs(localDefs, loc, cxt);
              IAbstract let = CafeSyntax.letExp(loc, localDefs, args.left());
              thetaIns.add(CafeSyntax.isDeclaration(loc, args.right(), let));
            }
          } else {
            if (access == AccessMode.readOnly)
              thetaIns.addAll(compileVarDeclaration(loc, defnPtn, access, value, cxt));
            else
              reportError("cannot handle assignment of " + defnPtn
                  + "\n suggest not making its value mutually recursive", vEntry.getLoc());
          }
        }
      } else if (stmt instanceof ImportEntry) {
        ImportEntry iEntry = (ImportEntry) stmt;

        pkgDefs.add(CafeSyntax.importSpec(loc, iEntry.getUri()));
      } else if (stmt instanceof JavaEntry) {
        JavaEntry jImport = (JavaEntry) stmt;
        thetaIns.add(CafeSyntax.javaImport(loc, jImport.getClassName()));
      } else if (stmt instanceof TypeDefinition) {
        TypeDefinition type = (TypeDefinition) stmt;
        IAlgebraicType desc = type.getTypeDescription();
        if (!type.isImported() && !types.contains(type.getName())) {
          generateType(desc, thetaIns, cxt);
          types.add(type.getName());
        }
      }

      assert scopeStack.peek() == thetaIns;
      scopeStack.pop();
    }

    IAbstract boundExp = bound.compileBound(thetaIns, doActions, cxt);

    cxt.resetDict(mark);
    return boundExp;
  }

  private Pair<IAbstract, IAbstract> findVarsInDefs(List<IAbstract> defs, Location loc, CContext cxt)
  {
    List<IAbstract> vars = new ArrayList<IAbstract>();
    List<IAbstract> args = new ArrayList<IAbstract>();
    List<IAbstract> types = new ArrayList<IAbstract>();

    for (IAbstract stmt : defs) {
      if (CafeSyntax.isIsDeclaration(stmt))
        findVarsInPtn(CafeSyntax.isDeclLval(stmt), vars, args, types);
      else if (CafeSyntax.isVarDeclaration(stmt))
        findVarsInPtn(CafeSyntax.varDeclLval(stmt), vars, args, types);
    }

    IAbstract tplType = CafeSyntax.tupleType(loc, types);
    IAbstract ptn = makeTuple(cxt, loc, tplType, args);
    IAbstract val = makeTuple(cxt, loc, tplType, vars);
    return Pair.pair(ptn, val);
  }

  private void findVarsInPtn(IAbstract ptn, List<IAbstract> vars, List<IAbstract> args, List<IAbstract> types)
  {
    if (CafeSyntax.isTypedTerm(ptn)) {
      vars.add(ptn);
      args.add(CafeSyntax.typedTerm(ptn));
      types.add(CafeSyntax.typedType(ptn));
    } else if (CafeSyntax.isConstructor(ptn))
      for (IValue arg : CafeSyntax.constructorArgs(ptn))
        findVarsInPtn((IAbstract) arg, vars, args, types);
  }

  private void generateType(IAlgebraicType desc, List<IAbstract> thetaIns, CContext cxt)
  {
    List<IAbstract> conSpecs = new ArrayList<IAbstract>();
    Location loc = desc.getLoc();

    for (IValueSpecifier spec : desc.getValueSpecifiers()) {
      List<IAbstract> conArgs = new ArrayList<IAbstract>();

      if (spec instanceof RecordSpecifier) {
        RecordSpecifier record = (RecordSpecifier) spec;
        Map<String, Integer> index = record.getIndex();
        TypeInterface members = record.getTypeInterface();
        for (int ix = 0; ix < record.arity(); ix++)
          conArgs.add(null); // fill out the arrow, so we can use set. :(

        for (Entry<String, IType> entry : members.getAllFields().entrySet()) {
          IType memberType = entry.getValue();
          String fldName = entry.getKey();
          Name field = new Name(loc, fldName);
          IAbstract fieldType = typeToAbstract(loc, cxt, memberType);
          Integer ix = index.get(fldName);
          conArgs.set(ix, CafeSyntax.typeCast(loc, field, fieldType));
        }

        conSpecs.add(CafeSyntax.recordSpec(loc, record.getLabel(), conArgs));
      } else {
        IType[] argTypes = TypeUtils.getConstructorArgTypes(TypeUtils.unwrap(spec.getConType()));

        for (int ix = 0; ix < argTypes.length; ix++) {
          IType argType = argTypes[ix];
          conArgs.add(typeToAbstract(loc, cxt, argType));
        }
        conSpecs.add(CafeSyntax.constructorSpec(loc, ((ConstructorSpecifier) spec).getLabel(), conArgs));
      }
    }
    thetaIns.add(CafeSyntax.typeDef(loc, typeToAbstract(loc, cxt, TypeUtils.unwrap(desc.getType())), conSpecs));
  }

  private void reportError(String msg, Location loc)
  {
    errors.reportError(msg, loc);
  }

  private void install(ExpressionGenerator compiler)
  {
    assert !expressionGenerators.containsKey(compiler.expressionClass());
    expressionGenerators.put(compiler.expressionClass(), compiler);
  }

  private interface IActionCompiler
  {
    List<IAbstract> compileAct(IContentAction act, CContext cxt);

    Class<? extends IContentAction> actionClass();
  }

  private void install(IActionCompiler compiler)
  {
    assert !actionGenerators.containsKey(compiler.actionClass());
    actionGenerators.put(compiler.actionClass(), compiler);
  }

  private interface PatternGenerator
  {
    IAbstract generatePtn(IContentPattern term, Wrapper<ICondition> after, AccessMode readOnly, CContext cxt);

    Class<? extends IContentPattern> generator();
  }

  private void install(PatternGenerator compiler)
  {
    assert !patternGenerators.containsKey(compiler.generator());

    patternGenerators.put(compiler.generator(), compiler);
  }

  private IAbstract makeTuple(CContext cxt, IType type, String label, Location loc, List<IAbstract> elExps)
  {
    return CafeSyntax.constructor(loc, label, elExps);
  }

  private IAbstract makeTuple(CContext cxt, Location loc, IAbstract tplType, List<IAbstract> els)
  {
    int arity = els.size();
    String label = TypeUtils.tupleLabel(arity);

    return CafeSyntax.constructor(loc, label, els);
  }

  private IAbstract unit(CContext cxt, Location loc)
  {
    return CafeSyntax.constructor(loc, NTuple.$0Enum.getLabel());
  }
}
